package com.csx.governance.traffic.route.swimlane;

import cn.hutool.core.util.StrUtil;
import com.csx.governance.traffic.condition.ParsedExpression;
import com.csx.governance.traffic.condition.support.ConditionMatcher;
import com.csx.governance.traffic.core.constants.TrafficAttributeConstants;
import com.csx.governance.traffic.core.context.TrafficRequest;
import com.csx.governance.traffic.core.context.TrafficResponse;
import com.csx.governance.traffic.core.interceptor.inbound.TrafficAbstractInboundHandlerInterceptor;
import com.csx.governance.traffic.core.support.TrafficMessageParameterExtractorRunner;
import com.csx.governance.traffic.core.utils.Weights;
import com.csx.governance.traffic.route.model.swimlane.SwimLaneRule;
import com.csx.governance.traffic.route.model.swimlane.SwimLaneServiceRule;
import com.csx.governance.traffic.route.model.swimlane.SwimLaneStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Map;

/**
 * 入口拦截器，进行泳道计算。
 * <p> 泳道计算即 将请求中携带的泳道ID取出，放入上下文中，供外调时筛选实例所用 (如果请求中未携带该泳道ID,
 * <p> 则读取泳道规则与当前请求进行匹配，从匹配的规则的对应的策略中根据权重计算出一个泳道)
 * @author cuisongxu
 * @date 2023/3/18 12:56
 */
public class SwimLaneResolverInboundHandlerInterceptor extends TrafficAbstractInboundHandlerInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(SwimLaneResolverInboundHandlerInterceptor.class);

    private final String localApplicationName;

    private final SwimLaneGovernanceRuleLocator swimLaneGovernanceRuleLocator;

    private final TrafficMessageParameterExtractorRunner trafficMessageParameterExtractorRunner;

    public SwimLaneResolverInboundHandlerInterceptor(String localApplicationName,
                                                     SwimLaneGovernanceRuleLocator swimLaneGovernanceRuleLocator,
                                                     TrafficMessageParameterExtractorRunner trafficMessageParameterExtractorRunner) {
        this.localApplicationName = localApplicationName;
        this.swimLaneGovernanceRuleLocator = swimLaneGovernanceRuleLocator;
        this.trafficMessageParameterExtractorRunner = trafficMessageParameterExtractorRunner;
    }

    @Override
    public boolean preHandle(TrafficRequest request, TrafficResponse response) {
        // 如果请求中携带了 泳道ID， 则直接透传下去，不需要额外做泳道计算
        String swimLaneId = request.getHeader(TrafficAttributeConstants.SWIM_LANE);
        if(StrUtil.isNotEmpty(swimLaneId)) {
            // 透传全链路灰度标识,放入到Attribute中
            request.setAttribute(TrafficAttributeConstants.SWIM_LANE, swimLaneId);
            return true;
        }

        // ******************************** 进行泳道计算 ********************************
        // 1. 获取当前应用 泳道服务规则
        SwimLaneServiceRule swimLaneServiceRule = swimLaneGovernanceRuleLocator.locateRule(localApplicationName);
        if(swimLaneServiceRule == null) {
            return true;
        }

        // 2. 获取满足条件的 泳道规则
        List<SwimLaneRule> rules = swimLaneServiceRule.getRules();
        Map<String, Object> params = trafficMessageParameterExtractorRunner.extract(request);
        SwimLaneRule targetSwimLaneRule = null;
        for (SwimLaneRule rule : rules) {
            ParsedExpression parsedExpression = rule.getConditionExpression().getParsedExpression();
            boolean match = ConditionMatcher.match(parsedExpression, params);
            if(match) {
                targetSwimLaneRule = rule;
                break;
            }
        }
        if(targetSwimLaneRule == null) {
            return true;
        }
        // 3. 获取泳道规则对应的泳道策略，并 根据权重 获取到 目标泳道ID
        SwimLaneStrategy.SwimLaneSubStrategy targetSwimLaneSubStrategy = Weights.getByRandomWeight(targetSwimLaneRule.getSwimLaneStrategy());
        String targetSwimLaneId = targetSwimLaneSubStrategy.getSwimLaneId();

        // 4. 透传目标泳道ID
        request.setHeader(TrafficAttributeConstants.SWIM_LANE, targetSwimLaneId);
        request.setAttribute(TrafficAttributeConstants.SWIM_LANE, targetSwimLaneId);
        logger.debug("request {} target swimLane: {}", request.getRequestURL(), targetSwimLaneId);
        return true;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
